package com.yuanda.erp9.syn.util.multiThread;

import com.yuanda.erp9.syn.contant.DownloadUrlConstants;
import com.yuanda.erp9.syn.exception.TargetServerException;
import com.yuanda.erp9.syn.util.multiThread.ext.FileResponseExtractor;
import com.yuanda.erp9.syn.util.multiThread.support.DownloadProgressPrinter;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;

import java.io.*;
import java.net.URLDecoder;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * @author zengqinglong
 * @desc 多线程下载文件
 * @Date 2023/1/17 11:13
 **/
public class MultiThreadFileUtils {
    private static int threadNum = 15;
    protected DownloadProgressPrinter downloadProgressPrinter;
    protected RestTemplate restTemplate;

    public MultiThreadFileUtils() {
        this.restTemplate = RestTemplateBuilder.builder().build();
        this.downloadProgressPrinter = DownloadProgressPrinter.defaultDownloadProgressPrinter();
    }

    /**
     * 下载
     * @param fileURL 下载地址
     * @param dir 保存地址
     * @param supplierId 供应商id
     * @param isUnZip 是否需要解压
     * @return
     */
    public String download(String fileURL, String dir, Integer supplierId, boolean isUnZip) {
        long start = System.currentTimeMillis();

        String decodeFileURL = null;
        try {
            decodeFileURL = URLDecoder.decode(fileURL, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        //通过Http协议的Head方法获取到文件的总大小
        HttpHeaders headers = new HttpHeaders();
        HttpEntity<String> requestEntity = new HttpEntity<>(null, headers);
        ResponseEntity<String> entity = restTemplate.exchange(decodeFileURL, HttpMethod.HEAD, requestEntity, String.class);
        String fileName = this.getFileName(decodeFileURL, entity.getHeaders());

        String path = doDownload(decodeFileURL, dir, fileName, entity.getHeaders(), supplierId, isUnZip);

        System.out.println("总共下载文件耗时:" + (System.currentTimeMillis() - start) / 1000 + "s");
        return path;
    }

    public String downloadAvnet(String fileURL, String dir, Integer supplierId, boolean isUnZip) {
        long start = System.currentTimeMillis();
        // 构造 post的body内容（要post的内容，按需定义）
        MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>();
        paramsMap.add("username", DownloadUrlConstants.AVNET_LOGIN_USER_NAME);
        paramsMap.add("password", DownloadUrlConstants.AVNET_LOGIN_PASSWORD);
        paramsMap.add("command", "login");
        paramsMap.add("skip_login", "true");
        paramsMap.add("encoded", "true");
        paramsMap.add("random", "");
        // 构造头部信息(若有需要)
        HttpHeaders headers = new HttpHeaders();
        // 设置类型 "application/json;charset=UTF-8"
        headers.setContentType(MediaType.APPLICATION_JSON);

        // 构造请求的实体。包含body和headers的内容
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity(paramsMap, headers);
        // 进行请求，并返回数据
        ResponseEntity<String> resp = restTemplate.exchange("https://b2b.avnet.com:8143/#/", HttpMethod.POST,request, String.class);
        List<String> cookie = resp.getHeaders().get("Set-Cookie");
        System.out.println("获取Cookie为：" + cookie);
        String decodeFileURL = null;
        try {
            decodeFileURL = URLDecoder.decode(fileURL, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        // header设置
        headers.put(HttpHeaders.COOKIE, cookie);
        headers.setContentType(MediaType.APPLICATION_JSON);
        //通过Http协议的Head方法获取到文件的总大小
        HttpEntity<String> requestEntity = new HttpEntity<>(null, headers);
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(decodeFileURL, String.class, requestEntity);
        String fileName = this.getFileName(decodeFileURL, responseEntity.getHeaders());

        String path = doDownload(decodeFileURL, dir, fileName, responseEntity.getHeaders(), supplierId, isUnZip);

        System.out.println("总共下载文件耗时:" + (System.currentTimeMillis() - start) / 1000 + "s");
        return path;
    }

    /**
     * 获取文件的名称
     *
     * @param fileURL
     * @return
     */
    private String getFileName(String fileURL, HttpHeaders headers) {
        String fileName = fileURL.substring(fileURL.lastIndexOf("/") + 1);
        if (fileName.contains(".")) {
            String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
            if (suffix.length() > 4 || suffix.contains("?")) {
                fileName = getFileNameFromHeader(headers);
            }
        } else {
            fileName = getFileNameFromHeader(headers);
        }
        return fileName;
    }

    private String getFileNameFromHeader(HttpHeaders headers) {
        String fileName = headers.getContentDisposition().getFilename();
        if (StringUtils.isEmpty(fileName)) {
            return UUID.randomUUID().toString();
        }
        return fileName;
    }
    protected String doDownload(String fileURL, String dir, String fileName, HttpHeaders headers, Integer supplierId, boolean isUnZip) {
        ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
        long contentLength = headers.getContentLength();
        downloadProgressPrinter.setContentLength(contentLength);

        //均分文件的大小
        long step = contentLength / threadNum;

        List<CompletableFuture<File>> futures = new ArrayList<>();
        for (int index = 0; index < threadNum; index++) {
            //计算出每个线程的下载开始位置和结束位置
            String start = step * index + "";
            String end = index == threadNum - 1 ? "" : (step * (index + 1) - 1) + "";

            String tempFilePath = dir + File.separator + "." + fileName + ".download." + index;
            FileResponseExtractor extractor = new FileResponseExtractor(index, tempFilePath, downloadProgressPrinter);

            CompletableFuture<File> future = CompletableFuture.supplyAsync(() -> {
                RequestCallback callback = request -> {
                    //设置HTTP请求头Range信息，开始下载到临时文件
                    request.getHeaders().add(HttpHeaders.RANGE, "bytes=" + start + "-" + end);
                };
                return restTemplate.execute(fileURL, HttpMethod.GET, callback, extractor);
            }, executorService).exceptionally(e -> {
                e.printStackTrace();
                return null;
            });
            futures.add(future);
        }
        String savePath = "";
        try {
            //创建最终文件
            String tmpFilePath = dir + File.separator + fileName + ".download";
            File file = new File(tmpFilePath);
            if (file != null && !file.exists()) {
                file.createNewFile();
            }
            FileChannel outChannel = new FileOutputStream(file).getChannel();
            futures.forEach(future -> {
                try {
                    File tmpFile = future.get();
                    FileChannel tmpIn = new FileInputStream(tmpFile).getChannel();
                    //合并每个临时文件
                    outChannel.transferFrom(tmpIn, outChannel.size(), tmpIn.size());
                    tmpIn.close();
                    tmpFile.delete(); //合并完成后删除临时文件
                } catch (InterruptedException | ExecutionException | IOException e) {
                    e.printStackTrace();
                }
            });
            outChannel.close();
            executorService.shutdown();
            savePath = dir + File.separator + fileName;
            File file1 = new File(savePath);
            if (file1 != null && file1.exists()) {
                file1.delete();
            }
            file.renameTo(file1);
        } catch (Exception e) {
            throw new TargetServerException("下载异常：" + e, supplierId, fileURL);
        }
        if (isUnZip) {
            try {
                System.out.println("解压文件中...");
                return unZipFiles(savePath, dir);
            } catch (Exception e) {
                e.printStackTrace();
                throw new TargetServerException("解压异常：" + e, supplierId, fileURL);
            }
        }
        return savePath;
    }

    /**
     * 解压的文件
     *
     * @param zipPath 压缩文件
     * @param descDir 解压存放的位置
     * @throws Exception
     */
    public static String unZipFiles(String zipPath, String descDir) throws Exception {
        System.out.println("解压文件的名称：" + zipPath + " 解压的文件存放路径：" + descDir);
        // 调用方法
        return zipDecompressing(zipPath, descDir);
    }


    /**
     * 解压
     *
     * @param directory
     * @param localPath
     * @return
     * @throws Exception
     */
    public static String zipDecompressing(String directory, String localPath) throws Exception {
        //输入源zip路径
        ZipInputStream Zin = new ZipInputStream(new FileInputStream(
                directory), Charset.forName("gbk"));
        BufferedInputStream Bin = new BufferedInputStream(Zin);
        try {
            //输出路径（文件夹目录）
            String parent = localPath;
            File fout = null;
            ZipEntry entry;
            String filePath = "";
            while ((entry = Zin.getNextEntry()) != null && !entry.isDirectory()) {
                fout = new File(parent, entry.getName());
                if (!fout.exists()) {
                    (new File(fout.getParent())).mkdirs();
                }
                FileOutputStream out = new FileOutputStream(fout);
                BufferedOutputStream Bout = new BufferedOutputStream(out);
                int b;
                while ((b = Bin.read()) != -1) {
                    Bout.write(b);
                }
                Bout.close();
                out.close();
                filePath = parent + entry.getName();
                System.out.println("文件解压成功,filePath:" + filePath);
            }
            return filePath;
        } finally {
            Bin.close();
            Zin.close();
        }
    }

}

